package com.korustar.autumnal.codemodel;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Random;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.ManagedMap;

import com.sun.codemodel.JClass;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JExpression;
import com.sun.codemodel.JInvocation;
import com.sun.codemodel.JVar;

/**
 * Builds parameters by type
 * 
 * @author amir
 *
 */
public class CodeModelParameterBuilder {
	
	private static Logger LOGGER= LoggerFactory.getLogger(CodeModelParameterBuilder.class);
	
	private final PropertyPlaceholderHelper propertyHelper = new PropertyPlaceholderHelper();
	
	
	public PropertyPlaceholderHelper getPropertyHelper() {
		return propertyHelper;
	}

	private JExpression buildString(String designatedValue, 
			ParameterContext context) {
		JExpression injectedValue;
		injectedValue = propertyHelper.calcOrGuessExpressionFromString(designatedValue,
				context.getExpectedType(), context.getDesignatedType(), context.getModel());
		return injectedValue;
	}
	
	private JExpression buildTypedStringValue(TypedStringValue designatedValue, 
			ParameterContext context) {
		return buildString(designatedValue.getValue(), context);
	}
	
	private JExpression buildRuntimeBeanReference(RuntimeBeanReference designatedValue, 
			ParameterContext context) {
		LOGGER.info("Bean Ref: {}",(designatedValue).getBeanName());
		return context.getModel().getBeanAsJFieldRef(
					(designatedValue).getBeanName() 
				);
	}
	private JExpression buildBeanDefinitionHolder(BeanDefinitionHolder designatedValue, 
			ParameterContext context) {
		if(!context.getModel().getConstructedBeanNames().contains(designatedValue.getBeanName())) {
			//construct it!
			LOGGER.warn("Trying to use an 'unconstructed' bean as a parameter!!!");
			throw new RuntimeException("Trying to use an unconstructed bean '"+designatedValue.getBeanName()+"' as a parameter");
		}
		return context.getModel().getBeanAsJFieldRef(designatedValue.getBeanName());
	}
	//primitives (boxing)
	private JExpression buildInteger(Integer designatedValue, 
			ParameterContext context) {
		return JExpr.lit(designatedValue);
	}
	private JExpression buildLong(Long designatedValue, 
			ParameterContext context) {
		return JExpr.lit(designatedValue);
	}
	private JExpression buildDouble(Double designatedValue, 
			ParameterContext context) {
		return JExpr.lit(designatedValue);
	}
	private JExpression buildFloat(Float designatedValue, 
			ParameterContext context) {
		return JExpr.lit(designatedValue);
	}
	private JExpression buildChar(Character designatedValue, 
			ParameterContext context) {
		return JExpr.lit(designatedValue);
	}
	private JExpression buildBoolean(Boolean designatedValue, 
			ParameterContext context) {
		return JExpr.lit(designatedValue);
	}
	private JExpression buildByte(Byte designatedValue, 
			ParameterContext context) {
		return JExpr.lit(designatedValue);
	}
	
	private JExpression buildManagedList(@SuppressWarnings("rawtypes") ManagedList designatedValue, 
			ParameterContext context) {
		//JBlock subBlock = currentBlock;			
		JClass ref = context.getModel().getCodeModel().ref(ArrayList.class);
		JVar decl = context.getCurrentBlock().decl(ref, "lst_"+ new Random().nextInt(1000000));
		JInvocation newList = JExpr._new(ref);
		context.getCurrentBlock().assign(decl, newList);
		for(Object listItem : designatedValue) {
			JExpression itemRef = buildParameter(listItem, new ParameterContext(context.getModel(), context.getCurrentBlock(), listItem.getClass(), null));
			JInvocation invocation2 = decl.invoke("add").arg(itemRef);
			context.getCurrentBlock().add(invocation2);
		}
		//currentBlock.add(subBlock);
		return decl;
	}
	
	private JExpression buildManagedMap(@SuppressWarnings("rawtypes") ManagedMap designatedValue, 
			ParameterContext context) {
		//JBlock subBlock = currentBlock;
		//ManagedMap map= designatedValue;
		JClass ref = context.getModel().getCodeModel().ref(LinkedHashMap.class);
		JVar decl = context.getCurrentBlock().decl(ref, "map_"+ new Random().nextInt(1000000));
		JInvocation newList = JExpr._new(ref);
		context.getCurrentBlock().assign(decl, newList);
		for(Object key : designatedValue.keySet()) {
			JExpression keyRef = buildParameter(key, new ParameterContext(context.getModel(), context.getCurrentBlock(), key.getClass(), null));
			JExpression itemRef = buildParameter(designatedValue.get(key), new ParameterContext(context.getModel(), context.getCurrentBlock(), designatedValue.get(key).getClass(), null));
			JInvocation invocation2 = decl.invoke("put").arg(keyRef).arg(itemRef);
			context.getCurrentBlock().add(invocation2);
		}
		return decl;
	}
	
	private JExpression guessParameter(Object designatedValue, 
			ParameterContext context) {
		JExpression injectedValue;
		LOGGER.info("Undefined handler for value - Class type: {}, value: {}",designatedValue.getClass(), designatedValue);
		injectedValue= CodeModelUtil.guessValueType(designatedValue, context.getDesignatedType());
		return injectedValue;
	}

	@SuppressWarnings("rawtypes")
	public JExpression buildParameter(Object designatedValue,
			ParameterContext context) {
		if(String.class.isAssignableFrom(designatedValue.getClass())) {
			return buildString((String) designatedValue, context);
		}
		if(TypedStringValue.class.isAssignableFrom(designatedValue.getClass())) {
			return buildTypedStringValue((TypedStringValue) designatedValue, context);
		}
		if(RuntimeBeanReference.class.isAssignableFrom(designatedValue.getClass())) {
			return buildRuntimeBeanReference((RuntimeBeanReference) designatedValue, context);
		}
		if(BeanDefinitionHolder.class.isAssignableFrom(designatedValue.getClass())) {
			return buildBeanDefinitionHolder((BeanDefinitionHolder) designatedValue, context);
		}
		if(ManagedList.class.isAssignableFrom(designatedValue.getClass())) {
			return buildManagedList((ManagedList) designatedValue, context);
		}
		if(ManagedMap.class.isAssignableFrom(designatedValue.getClass())) {
			return buildManagedMap((ManagedMap) designatedValue, context);
		}
		if(Integer.class.isAssignableFrom(designatedValue.getClass())) {
			return buildInteger((Integer) designatedValue, context);
		}
		if(Long.class.isAssignableFrom(designatedValue.getClass())) {
			return buildLong((Long) designatedValue, context);
		}
		if(Double.class.isAssignableFrom(designatedValue.getClass())) {
			return buildDouble((Double) designatedValue, context);
		}
		if(Float.class.isAssignableFrom(designatedValue.getClass())) {
			return buildFloat((Float) designatedValue, context);
		}
		if(Character.class.isAssignableFrom(designatedValue.getClass())) {
			return buildChar((Character) designatedValue, context);
		}
		if(Boolean.class.isAssignableFrom(designatedValue.getClass())) {
			return buildBoolean((Boolean) designatedValue, context);
		}
		if(Byte.class.isAssignableFrom(designatedValue.getClass())) {
			return buildByte((Byte) designatedValue, context);
		}
		return guessParameter(designatedValue, context);
	}





	
	
}
